/*
 * Copyright (c) 2022 The red-star Project
 *
 * Licensed under the Apache License, version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inyourcode.core.monitor;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.ScheduledReporter;
import com.codahale.metrics.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author JackLei
 */
public class MetricsService {
    private static final Logger LOGGER = LoggerFactory.getLogger(MetricsService.class);
    private static MetricsService instance = new MetricsService();
    private MetricRegistry metricRegistry;
    private ScheduledReporter reporter;
    private Map<String, Counter> counterMap = new ConcurrentHashMap<>();
    private Map<String, Meter> meterMap = new ConcurrentHashMap<>();
    private Map<String, Histogram> histogramMap = new ConcurrentHashMap<>();
    private Map<String, Timer> timerMap = new ConcurrentHashMap<>();

    public Map<String, Counter> getCounterMap() {
        return counterMap;
    }

    public Map<String, Meter> getMeterMap() {
        return meterMap;
    }

    public Map<String, Histogram> getHistogramMap() {
        return histogramMap;
    }

    public Map<String, Timer> getTimerMap() {
        return timerMap;
    }

    private MetricsService() {
        metricRegistry = new MetricRegistry();
    }

    public static MetricsService getInstance() {
        return instance;
    }

    public void start(String nodeName, RedisTemplate redisTemplate) {
        reporter = new MetricsRedisReport(nodeName, metricRegistry, redisTemplate, new ScheduledThreadPoolExecutor(1));
        this.reporter.start(10, TimeUnit.SECONDS);
    }

    public void stop() {
        this.reporter.stop();
    }

    private Counter addCounter(String name) {
        Counter counter = this.metricRegistry.counter(name);
        Counter putIfAbsent = this.counterMap.putIfAbsent(name, counter);
        if (putIfAbsent != null) {
            counter = putIfAbsent;
        }

        return counter;
    }

    private Counter getCounterWhenNullCreate(String name) {
        Counter counter = counterMap.get(name);
        if (counter != null) {
            return counter;
        }

        counter = addCounter(name);
        return counter;
    }

    public void incCount(String name) {
        getCounterWhenNullCreate(name).inc();
    }

    private Meter addMeter(String name) {
        Meter meter = this.metricRegistry.meter(name);
        Meter putIfAbsent = this.meterMap.putIfAbsent(name, meter);
        if (putIfAbsent != null) {
            meter = putIfAbsent;
        }
        return meter;
    }

    private Meter getMeterWhenNullCreate(String name) {
        Meter meter = this.meterMap.get(name);
        if (meter != null) {
            return meter;
        }

        meter = addMeter(name);
        return meter;
    }

    public void meterMark(String name) {
        Meter meter = getMeterWhenNullCreate(name);
        meter.mark();
    }

    private Timer addTimer(String name) {
        Timer timer = this.metricRegistry.timer(name);
        Timer putIfAbsent = this.timerMap.putIfAbsent(name, timer);
        if (putIfAbsent != null) {
            timer = putIfAbsent;
        }
        return timer;
    }

    private Timer getTimerWhenNullCreate(String name) {
        Timer timer = this.timerMap.get(name);
        if (timer != null) {
            return timer;
        }

        timer = addTimer(name);
        return timer;
    }

    public void timerUpdate(String name, long val, TimeUnit timeUnit) {
        getTimerWhenNullCreate(name).update(val, timeUnit);
    }

    private Histogram addHistogram(String name) {
        Histogram histogram = this.metricRegistry.histogram(name);
        Histogram putIfAbsent = this.histogramMap.putIfAbsent(name, histogram);
        if (putIfAbsent != null) {
            histogram = putIfAbsent;
        }

        return histogram;
    }

    private Histogram getHistogramWhenNullCreate(String name) {
        Histogram histogram = this.histogramMap.get(name);
        if (histogram != null) {
            return histogram;
        }

        histogram = addHistogram(name);
        return histogram;
    }

    public void updateHistogram(String name, int val) {
        getHistogramWhenNullCreate(name).update(val);
    }
}
